/*******************************************************************************
 * Copyright (c) 2012-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.api.vfs.impl.memory;

import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;

import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.ForbiddenException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.vfs.Archiver;
import org.eclipse.che.api.vfs.HashSumsCounter;
import org.eclipse.che.api.vfs.LockedFileFinder;
import org.eclipse.che.api.vfs.Path;
import org.eclipse.che.api.vfs.VirtualFile;
import org.eclipse.che.api.vfs.VirtualFileFilter;
import org.eclipse.che.api.vfs.VirtualFileSystem;
import org.eclipse.che.api.vfs.VirtualFileVisitor;
import org.eclipse.che.api.vfs.search.SearcherProvider;
import org.eclipse.che.commons.lang.NameGenerator;
import org.eclipse.che.commons.lang.Pair;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Maps.newHashMap;
import static java.util.Collections.singletonMap;

/**
 * In-memory implementation of VirtualFile.
 * <p/>
 * NOTE: This implementation is not thread safe.
 *
 * @author andrew00x
 */
public class MemoryVirtualFile implements VirtualFile {
    private static final Logger  LOG    = LoggerFactory.getLogger(MemoryVirtualFile.class);
    private static final boolean FILE   = false;
    private static final boolean FOLDER = true;

    static MemoryVirtualFile newFile(MemoryVirtualFile parent, String name, InputStream content) throws IOException {
        return new MemoryVirtualFile(parent, name, content == null ? new byte[0] : ByteStreams.toByteArray(content));
    }

    static MemoryVirtualFile newFile(MemoryVirtualFile parent, String name, byte[] content) {
        return new MemoryVirtualFile(parent, name, content == null ? new byte[0] : Arrays.copyOf(content, content.length));
    }

    static MemoryVirtualFile newFolder(MemoryVirtualFile parent, String name) {
        return new MemoryVirtualFile(parent, name);
    }

    //

    private final boolean                        type;
    private final Map<String, String>            properties;
    private final Map<String, MemoryVirtualFile> children;
    private final MemoryVirtualFileSystem        fileSystem;

    private String            name;
    private MemoryVirtualFile parent;
    private byte[]            content;
    private long              lastModificationDate;
    private LockHolder        lock;

    private boolean exists = true;

    // --- File ---
    private MemoryVirtualFile(MemoryVirtualFile parent, String name, byte[] content) {
        this.fileSystem = (MemoryVirtualFileSystem)parent.getFileSystem();
        this.parent = parent;
        this.type = FILE;
        this.name = name;
        this.properties = newHashMap();
        this.content = content;
        children = Collections.emptyMap();
    }

    // --- Folder ---
    private MemoryVirtualFile(MemoryVirtualFile parent, String name) {
        this.fileSystem = (MemoryVirtualFileSystem)parent.getFileSystem();
        this.parent = parent;
        this.type = FOLDER;
        this.name = name;
        this.properties = newHashMap();
        children = newHashMap();
    }

    // --- Root folder ---
    MemoryVirtualFile(VirtualFileSystem virtualFileSystem) {
        this.fileSystem = (MemoryVirtualFileSystem)virtualFileSystem;
        this.type = FOLDER;
        this.name = "";
        this.properties = newHashMap();
        children = newHashMap();
    }

    @Override
    public String getName() {
        checkExistence();
        return name;
    }

    @Override
    public Path getPath() {
        checkExistence();
        MemoryVirtualFile parent = this.parent;
        if (parent == null) {
            return Path.ROOT;
        }
        Path parentPath = parent.getPath();
        return parentPath.newPath(getName());
    }

    @Override
    public boolean isFile() {
        checkExistence();
        return type == FILE;
    }

    @Override
    public boolean isFolder() {
        checkExistence();
        return type == FOLDER;
    }

    @Override
    public boolean exists() {
        return exists;
    }

    @Override
    public boolean isRoot() {
        checkExistence();
        return parent == null;
    }

    @Override
    public long getLastModificationDate() {
        checkExistence();
        return lastModificationDate;
    }

    @Override
    public VirtualFile getParent() {
        checkExistence();
        return parent;
    }

    @Override
    public Map<String, String> getProperties() {
        checkExistence();
        return newHashMap(properties);
    }

    @Override
    public String getProperty(String name) {
        checkExistence();
        return properties.get(name);
    }

    @Override
    public VirtualFile updateProperties(Map<String, String> update, String lockToken) throws ForbiddenException {
        checkExistence();
        if (isFile() && fileIsLockedAndLockTokenIsInvalid(lockToken)) {
            throw new ForbiddenException(String.format("Unable update properties of item '%s'. Item is locked", getPath()));
        }
        for (Map.Entry<String, String> entry : update.entrySet()) {
            if (entry.getValue() == null) {
                properties.remove(entry.getKey());
            } else {
                properties.put(entry.getKey(), entry.getValue());
            }
        }
        lastModificationDate = System.currentTimeMillis();
        return this;
    }

    @Override
    public VirtualFile updateProperties(Map<String, String> properties) throws ForbiddenException, ServerException {
        return updateProperties(properties, null);
    }

    @Override
    public VirtualFile setProperty(String name, String value, String lockToken) throws ForbiddenException, ServerException {
        updateProperties(singletonMap(name, value), lockToken);
        return this;
    }

    @Override
    public VirtualFile setProperty(String name, String value) throws ForbiddenException, ServerException {
        return setProperty(name, value, null);
    }

    @Override
    public void accept(VirtualFileVisitor visitor) throws ServerException {
        checkExistence();
        visitor.visit(this);
    }

    @Override
    public List<Pair<String, String>> countMd5Sums() throws ServerException {
        checkExistence();
        if (isFile()) {
            return newArrayList();
        }

        return new HashSumsCounter(this, Hashing.md5()).countHashSums();
    }

    @Override
    public List<VirtualFile> getChildren(VirtualFileFilter filter) {
        checkExistence();
        if (isFolder()) {
            return doGetChildren(this).stream().filter(filter::accept).sorted().collect(Collectors.toList());
        }
        return newArrayList();
    }

    @Override
    public List<VirtualFile> getChildren() {
        checkExistence();
        if (isFolder()) {
            List<VirtualFile> children = doGetChildren(this);
            if (children.size() > 1) {
                Collections.sort(children);
            }
            return children;
        }
        return newArrayList();
    }

    private List<VirtualFile> doGetChildren(VirtualFile folder) {
        return newArrayList(((MemoryVirtualFile)folder).children.values());
    }

    @Override
    public boolean hasChild(Path path) throws ServerException {
        return getChild(path) != null;
    }

    @Override
    public VirtualFile getChild(Path path) throws ServerException {
        checkExistence();

        MemoryVirtualFile child = this;
        Iterator<String> pathSegments = newArrayList(path.elements()).iterator();
        while (pathSegments.hasNext() && child != null) {
            child = child.children.get(pathSegments.next());
        }
        if (pathSegments.hasNext()) {
            return null;
        }
        return child;
    }

    boolean addChild(MemoryVirtualFile child) {
        checkExistence();
        final String childName = child.getName();
        if (children.get(childName) == null) {
            children.put(childName, child);
            return true;
        }
        return false;
    }

    @Override
    public InputStream getContent() throws ForbiddenException {
        return new ByteArrayInputStream(getContentAsBytes());
    }

    @Override
    public byte[] getContentAsBytes() throws ForbiddenException {
        checkExistence();
        if (isFile()) {
            if (content == null) {
                content = new byte[0];
            }
            return Arrays.copyOf(content, content.length);
        }

        throw new ForbiddenException(String.format("We were unable to retrieve the content. Item '%s' is not a file", getPath()));
    }

    @Override
    public String getContentAsString() throws ForbiddenException {
        return new String(getContentAsBytes());
    }

    @Override
    public VirtualFile updateContent(InputStream content, String lockToken) throws ForbiddenException, ServerException {
        byte[] bytes;
        try {
            bytes = ByteStreams.toByteArray(content);
        } catch (IOException e) {
            throw new ServerException(String.format("We were unable to set the content of '%s'. Error: %s", getPath(), e.getMessage()));
        }
        doUpdateContent(bytes, lockToken);
        return this;
    }

    @Override
    public VirtualFile updateContent(byte[] content, String lockToken) throws ForbiddenException, ServerException {
        doUpdateContent(content, lockToken);
        return this;
    }

    @Override
    public VirtualFile updateContent(String content, String lockToken) throws ForbiddenException, ServerException {
        return updateContent(content.getBytes(), lockToken);
    }

    @Override
    public VirtualFile updateContent(byte[] content) throws ForbiddenException, ServerException {
        return updateContent(content, null);
    }

    @Override
    public VirtualFile updateContent(InputStream content) throws ForbiddenException, ServerException {
        return updateContent(content, null);
    }

    @Override
    public VirtualFile updateContent(String content) throws ForbiddenException, ServerException {
        return updateContent(content, null);
    }

    private void doUpdateContent(byte[] content, String lockToken) throws ForbiddenException, ServerException {
        checkExistence();

        if (isFile()) {
            if (fileIsLockedAndLockTokenIsInvalid(lockToken)) {
                throw new ForbiddenException(
                        String.format("We were unable to update the content of file '%s'. The file is locked", getPath()));
            }

            this.content = Arrays.copyOf(content, content.length);
            lastModificationDate = System.currentTimeMillis();

            updateInSearcher();
        } else {
            throw new ForbiddenException(String.format("We were unable to update the content. Item '%s' is not a file", getPath()));
        }
    }

    @Override
    public long getLength() {
        checkExistence();
        if (isFile()) {
            return content.length;
        }
        return 0;
    }

    @Override
    public VirtualFile copyTo(VirtualFile parent) throws ForbiddenException, ConflictException, ServerException {
        return copyTo(parent, null, false);
    }

    @Override
    public VirtualFile copyTo(VirtualFile parent, String newName, boolean overwrite)
            throws ForbiddenException, ConflictException, ServerException {
        checkExistence();
        ((MemoryVirtualFile)parent).checkExistence();
        if (isRoot()) {
            throw new ServerException("Unable copy root folder");
        }
        if (newName == null || newName.trim().isEmpty()) {
            newName = this.getName();
        }
        if (parent.isFolder()) {
            VirtualFile copy = doCopy((MemoryVirtualFile)parent, newName, overwrite);
            addInSearcher(copy);
            return copy;
        } else {
            throw new ForbiddenException(String.format("Unable create copy of '%s'. Item '%s' specified as parent is not a folder.",
                                                       getPath(), parent.getPath()));
        }
    }

    private VirtualFile doCopy(MemoryVirtualFile parent, String newName, boolean overwrite)
            throws ConflictException, ForbiddenException, ServerException {
        if (overwrite) {
            MemoryVirtualFile existedItem = parent.children.get(newName);
            if (existedItem != null) {
                existedItem.delete();
            }
        }

        MemoryVirtualFile virtualFile;
        if (isFile()) {
            virtualFile = newFile(parent, newName, Arrays.copyOf(content, content.length));
        } else {
            virtualFile = newFolder(parent, newName);
            for (VirtualFile child : getChildren()) {
                child.copyTo(virtualFile);
            }
        }

        virtualFile.properties.putAll(this.properties);

        if (parent.addChild(virtualFile)) {
            return virtualFile;
        }
        throw new ConflictException(String.format("Item '%s' already exists", parent.getPath().newPath(newName)));
    }

    @Override
    public VirtualFile moveTo(VirtualFile parent) throws ForbiddenException, ConflictException, ServerException {
        return moveTo(parent, null, false, null);
    }

    @Override
    public VirtualFile moveTo(VirtualFile parent, String newName, boolean overwrite, String lockToken)
            throws ForbiddenException, ConflictException, ServerException {
        checkExistence();
        MemoryVirtualFile memoryParent = (MemoryVirtualFile)parent;
        memoryParent.checkExistence();
        if (isRoot()) {
            throw new ForbiddenException("Unable move root folder");
        }
        if (!parent.isFolder()) {
            throw new ForbiddenException("Unable move item. Item specified as parent is not a folder");
        }
        if (newName == null || newName.trim().isEmpty()) {
            newName = this.getName();
        }
        final boolean isFile = isFile();
        final Path myPath = getPath();
        final Path newParentPath = parent.getPath();

        final boolean folder = isFolder();
        if (folder) {
            if (newParentPath.isChild(myPath)) {
                throw new ForbiddenException(
                        String.format("Unable move item %s to %s. Item may not have itself as parent", myPath, newParentPath));
            }
            final List<VirtualFile> lockedFiles = new LockedFileFinder(this).findLockedFiles();
            if (!lockedFiles.isEmpty()) {
                throw new ForbiddenException(
                        String.format("Unable move item '%s'. Child items '%s' are locked", getName(), lockedFiles));
            }
        } else if (fileIsLockedAndLockTokenIsInvalid(lockToken)) {
            throw new ForbiddenException(String.format("Unable move item %s. Item is locked", myPath));
        }

        if (overwrite) {
            MemoryVirtualFile existedItem = memoryParent.children.get(newName);
            if (existedItem != null) {
                existedItem.delete();
            }
        }

        if (memoryParent.children.containsKey(newName)) {
            throw new ConflictException(String.format("Item '%s' already exists", parent.getPath().newPath(newName)));
        }
        this.parent.children.remove(name);
        memoryParent.children.put(newName, this);
        this.parent = memoryParent;
        this.name = newName;
        lock = null;

        deleteFromSearcher(myPath, isFile);
        addInSearcher(this);
        return this;
    }

    @Override
    public VirtualFile rename(String newName, String lockToken) throws ForbiddenException, ConflictException, ServerException {
        checkExistence();
        checkName(newName);
        boolean isFile = isFile();
        if (isRoot()) {
            throw new ForbiddenException("We were unable to rename a root folder.");
        }
        final Path myPath = getPath();
        final boolean isFolder = isFolder();
        if (isFolder) {
            final List<VirtualFile> lockedFiles = new LockedFileFinder(this).findLockedFiles();
            if (!lockedFiles.isEmpty()) {
                throw new ForbiddenException(
                        String.format("Unable rename item '%s'. Child items '%s' are locked", getName(), lockedFiles));
            }
        } else {
            if (fileIsLockedAndLockTokenIsInvalid(lockToken)) {
                throw new ForbiddenException(String.format("We were unable to rename an item '%s'." +
                                                           " The item is currently locked by the system", getPath()));
            }
        }

        if (parent.children.get(newName) != null) {
            throw new ConflictException(String.format("Item '%s' already exists", newName));
        }
        parent.children.remove(name);
        parent.children.put(newName, this);
        name = newName;
        lock = null;

        lastModificationDate = System.currentTimeMillis();
        deleteFromSearcher(myPath, isFile);
        addInSearcher(this);
        return this;
    }

    @Override
    public VirtualFile rename(String newName) throws ForbiddenException, ConflictException, ServerException {
        return rename(newName, null);
    }

    @Override
    public void delete(String lockToken) throws ForbiddenException, ServerException {
        checkExistence();
        boolean isFile = isFile();
        if (isRoot()) {
            throw new ForbiddenException("Unable delete root folder");
        }
        final Path myPath = getPath();
        final boolean folder = isFolder();
        if (folder) {
            final List<VirtualFile> lockedFiles = new LockedFileFinder(this).findLockedFiles();
            if (!lockedFiles.isEmpty()) {
                throw new ForbiddenException(
                        String.format("Unable delete item '%s'. Child items '%s' are locked", getName(), lockedFiles));
            }
            for (VirtualFile virtualFile : getTreeAsList(this)) {
                ((MemoryVirtualFile)virtualFile).exists = false;
            }
        } else {
            if (fileIsLockedAndLockTokenIsInvalid(lockToken)) {
                throw new ForbiddenException(String.format("Unable delete item '%s'. Item is locked", getPath()));
            }
        }
        parent.children.remove(name);
        exists = false;
        parent = null;
        deleteFromSearcher(myPath, isFile);
    }

    List<VirtualFile> getTreeAsList(VirtualFile folder) throws ServerException {
        List<VirtualFile> list = newArrayList();
        folder.accept(new VirtualFileVisitor() {
            @Override
            public void visit(VirtualFile virtualFile) throws ServerException {
                if (virtualFile.isFolder()) {
                    for (VirtualFile child : virtualFile.getChildren()) {
                        child.accept(this);
                    }
                }
                list.add(virtualFile);
            }
        });
        return list;
    }

    @Override
    public void delete() throws ForbiddenException, ServerException {
        delete(null);
    }

    @Override
    public InputStream zip() throws ForbiddenException, ServerException {
        checkExistence();

        if (isFolder()) {
            return compress(fileSystem.getArchiverFactory().createArchiver(this, "zip"));
        } else {
            throw new ForbiddenException(String.format("Unable export to zip. Item '%s' is not a folder", getPath()));
        }
    }

    @Override
    public void unzip(InputStream zipped, boolean overwrite, int stripNumber)
            throws ForbiddenException, ServerException, ConflictException {
        checkExistence();

        if (isFolder()) {
            extract(fileSystem.getArchiverFactory().createArchiver(this, "zip"), zipped, overwrite, stripNumber);
            addInSearcher(this);
        } else {
            throw new ForbiddenException(String.format("Unable import zip. Item '%s' is not a folder", getPath()));
        }
    }

    @Override
    public InputStream tar() throws ForbiddenException, ServerException {
        checkExistence();

        if (isFolder()) {
            return compress(fileSystem.getArchiverFactory().createArchiver(this, "tar"));
        } else {
            throw new ForbiddenException(String.format("Unable export to tar archive. Item '%s' is not a folder", getPath()));
        }
    }

    @Override
    public void untar(InputStream tarArchive, boolean overwrite, int stripNumber)
            throws ForbiddenException, ConflictException, ServerException {
        checkExistence();

        if (isFolder()) {
            extract(fileSystem.getArchiverFactory().createArchiver(this, "tar"), tarArchive, overwrite, stripNumber);
            addInSearcher(this);
        } else {
            throw new ForbiddenException(String.format("Unable import tar archive. Item '%s' is not a folder", getPath()));
        }
    }

    private InputStream compress(Archiver archiver) throws ForbiddenException, ServerException {
        try {
            ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            archiver.compress(byteOut);
            return new ByteArrayInputStream(byteOut.toByteArray());
        } catch (IOException e) {
            throw new ServerException(e.getMessage(), e);
        }
    }

    private void extract(Archiver archiver, InputStream compressed, boolean overwrite, int stripNumber)
            throws ConflictException, ServerException, ForbiddenException {
        try {
            archiver.extract(compressed, overwrite, stripNumber);
        } catch (IOException e) {
            throw new ServerException(e.getMessage(), e);
        }
    }

    @Override
    public String lock(long timeout) throws ForbiddenException, ConflictException {
        checkExistence();
        if (isFile()) {
            if (this.lock != null) {
                throw new ConflictException("File already locked");
            }
            final String lockToken = NameGenerator.generate(null, 32);
            this.lock = new LockHolder(lockToken, timeout);
            lastModificationDate = System.currentTimeMillis();
            return lockToken;
        } else {
            throw new ForbiddenException(String.format("Unable lock '%s'. Locking allowed for files only", getPath()));
        }
    }

    @Override
    public VirtualFile unlock(String lockToken) throws ForbiddenException, ConflictException {
        checkExistence();
        if (isFile()) {
            final LockHolder theLock = lock;
            if (theLock == null) {
                throw new ConflictException("File is not locked");
            } else if (isExpired(theLock)) {
                lock = null;
                throw new ConflictException("File is not locked");
            }
            if (theLock.lockToken.equals(lockToken)) {
                lock = null;
                lastModificationDate = System.currentTimeMillis();
            } else {
                throw new ForbiddenException("Unable remove lock from file. Lock token does not match");
            }
            lastModificationDate = System.currentTimeMillis();
            return this;
        } else {
            throw new ForbiddenException(String.format("Unable unlock '%s'. Locking allowed for files only", getPath()));
        }
    }

    @Override
    public boolean isLocked() {
        checkExistence();
        final LockHolder myLock = lock;
        if (myLock != null) {
            if (isExpired(myLock)) {
                lock = null;
                return false;
            }
            return true;
        }
        return false;
    }

    private boolean isExpired(LockHolder lockHolder) {
        return lockHolder.expired < System.currentTimeMillis();
    }

    @Override
    public VirtualFile createFile(String name, InputStream content) throws ForbiddenException, ConflictException, ServerException {
        checkExistence();

        checkName(name);
        if (Path.of(name).length() > 1) {
            throw new ServerException(String.format("Invalid name '%s'", name));
        }

        if (isFolder()) {
            final MemoryVirtualFile newFile;
            try {
                newFile = newFile(this, name, content);
            } catch (IOException e) {
                throw new ServerException(String.format("Unable set content of '%s'. Error: %s", getPath(), e.getMessage()));
            }
            if (!addChild(newFile)) {
                throw new ConflictException(String.format("Item with the name '%s' already exists", name));
            }
            addInSearcher(newFile);
            return newFile;
        } else {
            throw new ForbiddenException("Unable create new file. Item specified as parent is not a folder");
        }
    }

    @Override
    public VirtualFile createFile(String name, byte[] content) throws ForbiddenException, ConflictException, ServerException {
        return createFile(name, new ByteArrayInputStream(content));
    }

    @Override
    public VirtualFile createFile(String name, String content) throws ForbiddenException, ConflictException, ServerException {
        return createFile(name, content.getBytes());
    }

    @Override
    public VirtualFile createFolder(String name) throws ForbiddenException, ConflictException, ServerException {
        checkExistence();
        checkName(name);
        if (name.charAt(0) == '/') {
            name = name.substring(1);
        }
        checkName(name);
        if (isFolder()) {
            MemoryVirtualFile newFolder = null;
            MemoryVirtualFile current = this;
            if (name.indexOf('/') > 0) {
                final Path internPath = Path.of(name);
                for (String element : internPath.elements()) {
                    MemoryVirtualFile folder = newFolder(current, element);
                    if (current.addChild(folder)) {
                        newFolder = folder;
                        current = folder;
                    } else {
                        current = current.children.get(element);
                    }
                }
                if (newFolder == null) {
                    throw new ConflictException(String.format("Item with the name '%s' already exists", name));
                }
            } else {
                newFolder = newFolder(this, name);
                if (!addChild(newFolder)) {
                    throw new ConflictException(String.format("Item with the name '%s' already exists", name));
                }
            }
            return newFolder;
        } else {
            throw new ForbiddenException("Unable create new folder. Item specified as parent is not a folder");
        }
    }

    @Override
    public java.io.File toIoFile() {
        return null;
    }

    @Override
    public VirtualFileSystem getFileSystem() {
        return fileSystem;
    }

    @Override
    public int compareTo(VirtualFile o) {
        // To get nice order of items:
        // 1. Regular folders
        // 2. Files
        if (o == null) {
            throw new NullPointerException();
        }
        if (isFolder()) {
            return o.isFolder() ? getName().compareTo(o.getName()) : -1;
        } else if (o.isFolder()) {
            return 1;
        }
        return getName().compareTo(o.getName());
    }

    @Override
    public String toString() {
        return getPath().toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (!(o instanceof MemoryVirtualFile)) {
            return false;
        }
        MemoryVirtualFile other = (MemoryVirtualFile)o;
        return Objects.equals(fileSystem, other.fileSystem)
               && Objects.equals(getPath(), other.getPath());
    }

    @Override
    public int hashCode() {
        return Objects.hash(fileSystem, getPath());
    }

    private void checkExistence() {
        if (!exists) {
            throw new RuntimeException(String.format("Item '%s' already removed", name));
        }
    }

    private void checkName(String name) throws ServerException {
        if (name == null || name.trim().isEmpty()) {
            throw new ServerException("Item's name is not set");
        }
    }

    private void addInSearcher(VirtualFile newFile) {
        SearcherProvider searcherProvider = fileSystem.getSearcherProvider();
        if (searcherProvider != null) {
            try {
                searcherProvider.getSearcher(fileSystem).add(newFile);
            } catch (ServerException e) {
                LOG.error(e.getMessage(), e);
            }
        }
    }

    private void updateInSearcher() {
        SearcherProvider searcherProvider = fileSystem.getSearcherProvider();
        if (searcherProvider != null) {
            try {
                searcherProvider.getSearcher(fileSystem).update(this);
            } catch (ServerException e) {
                LOG.error(e.getMessage(), e);
            }
        }
    }

    private void deleteFromSearcher(Path path, boolean isFile) {
        SearcherProvider searcherProvider = fileSystem.getSearcherProvider();
        if (searcherProvider != null) {
            try {
                searcherProvider.getSearcher(fileSystem).delete(path.toString(), isFile);
            } catch (ServerException e) {
                LOG.error(e.getMessage(), e);
            }
        }
    }

    private boolean fileIsLockedAndLockTokenIsInvalid(String lockToken) {
        if (isLocked()) {
            final LockHolder myLock = lock;
            return myLock != null && !myLock.lockToken.equals(lockToken);
        }
        return false;
    }

    private static class LockHolder {
        final String lockToken;
        final long   expired;

        LockHolder(String lockToken, long timeout) {
            this.lockToken = lockToken;
            this.expired = timeout > 0 ? (System.currentTimeMillis() + timeout) : Long.MAX_VALUE;
        }
    }
}
